//*******************************************************************************
// COPYRIGHT NOTES
// ---------------
// This is a sample for BCGControlBar Library Professional Edition
// Copyright (C) 1998-2016 BCGSoft Ltd.
// All rights reserved.
//
// This source code can be used, distributed or modified
// only under terms and conditions 
// of the accompanying license agreement.
//*******************************************************************************
//
// ChartCustomAxisView.cpp : implementation file
//

#include "stdafx.h"
#include "bcgpchartexample.h"
#include "ChartCustomAxisView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

static const int AXIS_GAP = 10;
static const int ADDITIONAL_GAP = 30;

//************************************************************
// Custom axis to handle label formatting
//************************************************************
class CCustomYAxisBase : public CBCGPChartAxisY
{
	DECLARE_DYNCREATE(CCustomYAxisBase)

public:
	CCustomYAxisBase()
	{
	}

	CCustomYAxisBase(int nAxisID, CBCGPChartAxis::AxisDefaultPosition position, CBCGPChartVisualObject* pChartCtrl) :
				CBCGPChartAxisY(nAxisID, position, pChartCtrl)
	{
	}

	virtual void GetDisplayedLabel(double dblValue, CString& strLabel);
};

IMPLEMENT_DYNCREATE(CCustomYAxisBase, CBCGPChartAxisY)

//------------------------------------------------------
// Displays label in format n,nnn.nn
//------------------------------------------------------
void CCustomYAxisBase::GetDisplayedLabel(double dblValue, CString& strLabel)
{
	strLabel.Format(_T("%.2f"), dblValue);
	
	int nPointIndex = strLabel.Find('.');
	
	for (int nIndex = nPointIndex - 3; nIndex > 0; nIndex -= 3)
	{
		if (nPointIndex <= 0)
		{
			break;
		}
		
		strLabel.Insert(nIndex, _T(","));
	}
}

//************************************************************
// Custom axis to be displayed outside of the Plot Area
//************************************************************
class CCustomAxisY : public CCustomYAxisBase
{
public:
	CCustomAxisY(int nAxisID, CBCGPChartAxis::AxisDefaultPosition position, CBCGPChartVisualObject* pChartCtrl) :
		CCustomYAxisBase(nAxisID, position, pChartCtrl)
	{

	}

	virtual void CalcAxisPos(const CBCGPRect& rectDiagram, BOOL bStoreAxisPos);
};

//------------------------------------------------------
// Calculates custom axis X offset based on the legend position
//------------------------------------------------------
void CCustomAxisY::CalcAxisPos(const CBCGPRect& rectDiagram, BOOL bStoreAxisPos)
{
	if (m_pChart == NULL)
	{
		return;
	}

	CBCGPChartAxisY::CalcAxisPos(rectDiagram, bStoreAxisPos);

	// assume we align axis relative to legend, which is located at the right
	// if the legend position doesn't matter use rectDiagram
	CBCGPRect rectLegend = m_pChart->GetLegendArea();

	// assumes the axis is vertical
	if ((m_pChart->m_chartLayout.m_legendPosition == BCGPChartLayout::LP_RIGHT || 
		m_pChart->m_chartLayout.m_legendPosition == BCGPChartLayout::LP_TOPRIGHT) && 
		!m_pChart->m_chartLayout.m_bLegendOverlapsChart)
	{
		m_ptAxisStart.x = m_ptAxisEnd.x = rectLegend.left - ADDITIONAL_GAP - m_szMaxLabelSize.cx;
	}
	else
	{
		// rectDiagram contains current plot area rect
		CBCGPChartAxis* pSecondaryAxis = m_pChart->GetChartAxis(BCGP_CHART_Y_SECONDARY_AXIS);
		m_ptAxisStart.x = m_ptAxisEnd.x = rectDiagram.right + ADDITIONAL_GAP + 
			AXIS_GAP + pSecondaryAxis->m_szMaxLabelSize.cx;
	}
}

//************************************************************
// Custom chart that adjusts plot area to accommodate space for third custom axis
//************************************************************
class CCustomChart : public CBCGPChartVisualObject
{
	DECLARE_DYNCREATE(CCustomChart)

public:
	CCustomChart()
	{
		m_nCustomAxisID = -1;
		m_bInsideAdjustLayout = FALSE;
		m_bFirstAdjust = TRUE;
	}

	CBCGPChartAxis* AddCustomAxis();
	virtual BOOL OnGetPlotAreaRect(CBCGPRect& rectPlotArea) const;
	
	virtual void AdjustLayout(CBCGPGraphicsManager* pGM);
	virtual void OnScaleRatioChanged(const CBCGPSize& sizeScaleRatioOld)
	{
		CBCGPChartVisualObject::OnScaleRatioChanged(sizeScaleRatioOld);
		m_bFirstAdjust = TRUE;
	}

protected:
	int m_nCustomAxisID;
	BOOL m_bInsideAdjustLayout;
	CBCGPRect m_rectCustomPlotArea;
	BOOL m_bFirstAdjust;
};

IMPLEMENT_DYNCREATE(CCustomChart, CBCGPChartVisualObject)

//------------------------------------------------------
// Adds custom Y axis and stores its index
//------------------------------------------------------
CBCGPChartAxis* CCustomChart::AddCustomAxis()
{
	// we could store a pointer to custom axis, but storing ID will gracefully handle removing (just in case)
	// in OnGetPlotAreaRect
	// CBCGPChartAxis::ADP_RIGHT tells the axis show labels at the right side by default
	CBCGPChartAxis* pAxis = new CCustomAxisY(GetNextCustomAxisID(), CBCGPChartAxis::ADP_RIGHT, this);
	m_nCustomAxisID = pAxis->m_nAxisID;

	// add the custom axis to collection
	AddAxis(pAxis);

	return pAxis;
}

//------------------------------------------------------
// Adjust layout must be called 2 times when the chart is displayed for the first time,
// because plot area rectangle is based on other elements
//------------------------------------------------------
void CCustomChart::AdjustLayout(CBCGPGraphicsManager* pGM)
{
	m_bInsideAdjustLayout = TRUE;
	CBCGPChartVisualObject::AdjustLayout(pGM);

	if (m_bFirstAdjust)
	{
		m_bFirstAdjust = FALSE;
		CBCGPChartVisualObject::AdjustLayout(pGM);
	}
	m_bInsideAdjustLayout = FALSE;

	// store the plot area rect to return it without additional calculations
	// when the rect is requested from methods other than AdjustLayout
	m_rectCustomPlotArea = m_rectPlotArea;
}

//------------------------------------------------------
// Returns plot area rectangle adjusted to make additional space for third axis
//------------------------------------------------------
BOOL CCustomChart::OnGetPlotAreaRect(CBCGPRect& rectPlotArea) const
{
	if (!m_bInsideAdjustLayout && !m_rectCustomPlotArea.IsRectEmpty())
	{
		rectPlotArea = m_rectCustomPlotArea;
		return TRUE; 
	}

	CBCGPRect rectLegend = m_rectLegendArea;

	if (m_bFirstAdjust || m_rectTitleArea.IsRectEmpty() || m_nCustomAxisID == -1 || m_rect.IsRectEmpty())
	{
		// AdjustLayout has not been called or custom axis has not been created
		return FALSE;
	}

	CBCGPChartAxis* pCustomAxis = GetChartAxis(m_nCustomAxisID);
	if (pCustomAxis == NULL || pCustomAxis->m_szMaxLabelSize.cx == 0)
	{
		return FALSE;
	}

	CBCGPRect rectPlot = m_rect;
	rectPlot.DeflateRect(m_chartAreaFormat.GetContentPadding(TRUE));

	if (!m_chartLayout.m_bTitleOverlapsChart)
	{
		rectPlot.top = m_rectTitleArea.bottom;
	}

	// if the legend is at right or top right and does not overlaps chart!
	if (!m_chartLayout.m_bLegendOverlapsChart)
	{
		CBCGPSize szLegendAreaPadding = GetLegendAreaPadding(TRUE);

		if (m_chartLayout.m_legendPosition == BCGPChartLayout::LP_RIGHT || 
			m_chartLayout.m_legendPosition == BCGPChartLayout::LP_TOPRIGHT)
		{
			rectPlot.right = m_rect.right - szLegendAreaPadding.cx - rectLegend.Width();
		}
		else if (m_chartLayout.m_legendPosition == BCGPChartLayout::LP_LEFT)
		{
			rectPlot.left = rectLegend.right + szLegendAreaPadding.cx;  
		}
		else if (m_chartLayout.m_legendPosition == BCGPChartLayout::LP_TOP)
		{
			rectPlot.top = rectLegend.bottom + szLegendAreaPadding.cy;  
		}
		else if (m_chartLayout.m_legendPosition == BCGPChartLayout::LP_BOTTOM)
		{
			rectPlot.bottom = rectLegend.top - szLegendAreaPadding.cy; 
		}
	}
	
	rectPlot.right -= pCustomAxis->m_szMaxLabelSize.cx + AXIS_GAP + ADDITIONAL_GAP;
	rectPlotArea = rectPlot;

	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CChartCustomAxisView

IMPLEMENT_DYNCREATE(CChartCustomAxisView, CBCGPChartExampleView)

CChartCustomAxisView::CChartCustomAxisView()
	: CBCGPChartExampleView(CChartCustomAxisView::IDD)
{
	//{{AFX_DATA_INIT(CChartCustomAxisView)
		// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT

	m_wndChart.CreateCustomChart(RUNTIME_CLASS(CCustomChart));
}

CChartCustomAxisView::~CChartCustomAxisView()
{
}

void CChartCustomAxisView::DoDataExchange(CDataExchange* pDX)
{
	CBCGPChartExampleView::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CChartCustomAxisView)
	DDX_Control(pDX, IDC_CHART, m_wndChart);
	//}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(CChartCustomAxisView, CBCGPChartExampleView)
	//{{AFX_MSG_MAP(CChartCustomAxisView)
		// NOTE - the ClassWizard will add and remove mapping macros here.
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CChartCustomAxisView diagnostics

#ifdef _DEBUG
void CChartCustomAxisView::AssertValid() const
{
	CBCGPChartExampleView::AssertValid();
}

void CChartCustomAxisView::Dump(CDumpContext& dc) const
{
	CBCGPChartExampleView::Dump(dc);
}
#endif //_DEBUG


/////////////////////////////////////////////////////////////////////////////
// CChartCustomAxisView message handlers

void CChartCustomAxisView::OnInitialUpdate() 
{
	CBCGPChartExampleView::OnInitialUpdate();
	
	if (m_bIsReady)
	{
		return;
	}

	m_bIsReady = TRUE;

	CCustomChart* pChart = DYNAMIC_DOWNCAST(CCustomChart, m_wndChart.GetChart());
	ASSERT_VALID(pChart);

	CBCGPChartSeries* pSeries1 = pChart->CreateSeries(_T("Column"), CBCGPColor(), BCGP_CT_DEFAULT, BCGPChartColumn);
	CBCGPChartSeries* pSeries2 = pChart->CreateSeries(_T("Line1"), CBCGPColor(), BCGP_CT_DEFAULT, BCGPChartLine);
	CBCGPChartSeries* pSeries3 = pChart->CreateSeries(_T("Line2"), CBCGPColor(), BCGP_CT_DEFAULT, BCGPChartLine);

	CBCGPChartAxis* pPrimaryXAxis = pChart->GetChartAxis(BCGP_CHART_X_PRIMARY_AXIS);
	CBCGPChartAxis* pPrimaryYAxis = pChart->GetChartAxis(BCGP_CHART_Y_PRIMARY_AXIS);

	// replace default Y axis to handle custom labels
	CBCGPChartAxis* pSecondaryYAxis = pChart->ReplaceDefaultAxis(BCGP_CHART_Y_SECONDARY_AXIS, RUNTIME_CLASS(CCustomYAxisBase));

	// create custom third Y axis
	CBCGPChartAxis* pCustomAxis = pChart->AddCustomAxis();

	pPrimaryXAxis->m_axisLineFormat.m_dblWidth = 2.;
	pPrimaryYAxis->m_axisLineFormat.m_dblWidth = 2.;
	pSecondaryYAxis->m_axisLineFormat.m_dblWidth = 2.;
	pCustomAxis->m_axisLineFormat.m_dblWidth = 2.;

	pSeries1->AddDataPoint(_T("Item 1"), 35.);
	pSeries1->AddDataPoint(_T("Item 2"), 40.);
	pSeries1->AddDataPoint(_T("Item 3"), 85.);
	pSeries1->AddDataPoint(_T("Item 4"), 37);
	pSeries1->AddDataPoint(_T("Item 5"), 55.);
	pSeries1->AddDataPoint(_T("Item 6"), 48.);

	pSeries2->AddDataPoint(2000.);
	pSeries2->AddDataPoint(7500.);
	pSeries2->AddDataPoint(9900.);
	pSeries2->AddDataPoint(600.);
	pSeries2->AddDataPoint(3000.);
	pSeries2->AddDataPoint(12000.);

	pSeries3->AddDataPoint(40000000.);
	pSeries3->AddDataPoint(60000000.);
	pSeries3->AddDataPoint(71000000.);
	pSeries3->AddDataPoint(33000000.);
	pSeries3->AddDataPoint(28000000.);
	pSeries3->AddDataPoint(73000000.);

	// display second series on the secondary Y axis
	pSeries2->SetRelatedAxis(pSecondaryYAxis, CBCGPChartSeries::AI_Y);

	// display third series on the custom third axis
	pSeries3->SetRelatedAxis(pCustomAxis, CBCGPChartSeries::AI_Y);

	// set fixed display ranges with fixed major unit, because automatic ranges 
	// will look a bit different
	pPrimaryYAxis->SetFixedDisplayRange(0., 150., 25.);
	pSecondaryYAxis->SetFixedDisplayRange(0., 30000., 10000.);
	pCustomAxis->SetFixedDisplayRange(0., 90000000, 10000000.);

	// setup marker visibility
	pSeries1->ShowMarker(FALSE);
	pSeries2->ShowMarker(TRUE);
	pSeries2->SetMarkerShape(BCGPChartMarkerOptions::MS_CIRCLE);
	pSeries3->ShowMarker(TRUE);
	pSeries3->SetMarkerShape(BCGPChartMarkerOptions::MS_RECTANGLE);

	// set second series to be SPLINE with width 2.
	BCGPChartFormatSeries style = pSeries2->GetSeriesFormat();

	style.m_curveType = BCGPChartFormatSeries::CCT_SPLINE;
	style.SetSeriesLineWidth(2.);
	pSeries2->SetSeriesFormat(style);

	// set width for the third series
	pSeries3->SetSeriesLineWidth(2.);

	// relocate the legend at the top
	pChart->SetLegendPosition(BCGPChartLayout::LP_TOPRIGHT);

	// set title font to be smaller
	pChart->GetTitleLabelFormat().m_textFormat.SetFontSize(18.);

	// do not display vertical interlaces
	pChart->ShowAxisIntervalInterlacing(BCGP_CHART_X_PRIMARY_AXIS, FALSE);

	// Sync series and axis colors
	OnColorThemeUpdated();

	SetDefaultLineWidth();
}

void CChartCustomAxisView::OnColorThemeUpdated()
{
	CCustomChart* pChart = DYNAMIC_DOWNCAST(CCustomChart, m_wndChart.GetChart());
	ASSERT_VALID(pChart);

	for (int i = 0; i < pChart->GetSeriesCount(); i++)
	{
		CBCGPChartSeries* pSeries = pChart->GetSeries(i, FALSE);

		if (pSeries == NULL)
		{
			continue;
		}

		BCGPSeriesColorsPtr seriesColors;

		// get base series colors
		pSeries->GetColors(seriesColors, -1); 

		// set axis color to series color
		CBCGPChartAxis* pAxis = pSeries->GetRelatedAxis(CBCGPChartSeries::AI_Y);

		pAxis->m_axisLineFormat.m_brLineColor = *seriesColors.m_pBrElementLineColor;
		pAxis->m_axisLabelsFormat.m_brTextColor = *seriesColors.m_pBrElementLineColor;
	}
}

void CChartCustomAxisView::UpdateChartColorTheme(int nTheme, BOOL bIsDarkTheme)
{
	CBCGPChartExampleView::UpdateChartColorTheme(nTheme, bIsDarkTheme);

	OnColorThemeUpdated();
}

